/*
 * Copyright the original author or authors.
 * 
 * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.mozilla.org/MPL/MPL-1.1.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.kairos.motion
{
	import com.bourre.commands.ASyncCommandListener;
	import com.bourre.structures.Range;
	import com.kairos.log.KairosDebug;
	import com.kairos.motion.strategy.AdvancedLoopStrategy;
	import com.kairos.motion.strategy.AdvancedReversedLoopStrategy;
	import com.kairos.motion.strategy.PlayStrategy;	

	/**
	 * <code>AbstractAdvancedAnimation</code> offers you to control
	 * the number of loops the animation perform and the frame range
	 * wherein it can play. Whatsoever the time's direction of the
	 * animation, by using the <code>loopCount</code> and the
	 * <code>range</code> parameters.
	 * <p>
	 * For achieving its work the animation use two customs strategy
	 * that put together <i>loop</i> and <i>noloop</i> strategies.
	 * </p><p>
	 * The range parameter could be really usefull to deal with animated
	 * sprites in games, thus you can organize all the differents sprites
	 * animation on a single timeline, and then change the range to change
	 * the animation, without having to destroy and create objects each
	 * time you want to change the animation.
	 * </p>
	 * 
	 * @author Cédric Néhémie
	 */
	public class AbstractAdvancedAnimation extends AbstractAnimation
	{
		/**
		 * Strategy for restricted looping animation in the normal time's arrow.
		 */
		static protected const ADVANCED_LOOP : PlayStrategy = new AdvancedLoopStrategy();
		/**
		 * Strategy for restricted looping animation in the reversed time's arrow.
		 */
		static protected const ADVANCED_REVERSED_LOOP : PlayStrategy = new AdvancedReversedLoopStrategy();
		/**
		 * The frame range into which the animation run.
		 */
		protected var _oPlayRange : Range;
		/**
		 * The number of loop to perform before stop.
		 */
		protected var _nLoopCount : Number;
		/**
		 * The current performed loop number.
		 */
		protected var _nLoopNum : Number;
		/**
		 * The number of frame in the animation range.
		 * Used to accelerate computation.
		 */
		protected var _nRangeLength : Number;
		/**
		 * The first frame in the range.
		 * Used to accelerate computation.
		 */
		protected var _nRangeStart : Number;
		
		/**
		 * Configures the animation as you configure the
		 * <code>AbstractAnimation</code> but the <i>loop</i>
		 * parameter is replaced by <code>loopCount</code>
		 * and <code>range</code> parameters.
		 * <p>
		 * If <code>loopCount</code> is lower or equals than
		 * <code>0</code> then the animation run indefinitely.
		 * See the <a href="#setLoopCount()">setLoopCount</a>
		 * method for details about looping rules.
		 * </p><p>
		 * If <code>range</code> is omitted the animation will
		 * play on the totality of the animation's frames. See
		 * the <a href="#setRange()">setRange</a> method to have
		 * details on how ranges works and how defines them correctly.
		 * </p><p>
		 * If <code>reverse</code> argument is omitted then
		 * <code>reverse</code> is set to <code>false</code>.
		 * </p>
		 * @param   fps 	  speed of the animation in frame per second
		 * @param	loopCount number of loops before the animation stop
		 * @param	range 	  frames range within which the animation run
		 * @param	r		  <code>true</code> to make the movieclip playing in
		 * 					  the reversed arrow of time  
		 */
		public function AbstractAdvancedAnimation( fps : Number = 40, loopCount : uint = 0, range : Range = null, r : Boolean = false)
		{
			super( fps, true, r );
			setLoopCount( loopCount );
			setRange ( range );
		}
		/**
		 * Resets the internal loop counter. 
		 * <p>
		 * The frame, onto which the animation is, don't
		 * changed on a call, its just affect the loop count.
		 * Use the <code>reset</code> method to reset the whole
		 * animation states.
		 * </p>
		 */
		public function resetCount() : void
		{
			_nLoopNum = 0;
		}
		
		/**
		 * Resets the animation state.
		 * <p>
		 * If the object is running the animation just restart
		 * from the first or the last frame according to the object
		 * play mode.
		 * </p><p>
		 * The loop counter is also reset as result of the call.
		 * </p>
		 */
		 override public function reset() : void
		 {
		 	super.reset();
		 	resetCount();
		 	
		 	_nFrame = _oStrategy.getInitialFrame( _nRangeLength );
			
		 	_frameChanged( _oPlayRange.min + _nFrame );
		}
		
		/**
		 * Defines the frame range wherein the animation can play.
		 * <p>
		 * Animation will play in the range from <code>min</code>
		 * (included) and </code>max</code> (included). As movies
		 * frames are defined with 1-based indexes the range
		 * is also defined in the same way. Looping animations
		 * automatically go back to the first frame of the range
		 * when arriving to the right side of the range.
		 * </p><p>
		 * Range must be contained in the original moveclip timeline
		 * range, if the passed-in range overlap the valid play range, 
		 * it will be cropped to respect the timeline limits.
		 * </p>
		 * 
		 * @param	range	frames range within which the animation run
		 * @throws 	<code>RangeError</code> — The range(min,max) must
		 * 			have a length greater or equals to 1. 
		 */
		public function setRange ( range : Range = null ) : void
		{
			if ( !range ) 
				range = new Range ( 1, _nTotalFrames );
			
			_oPlayRange = range; 
			
			if( _oPlayRange.max - _oPlayRange.min == 0 )
				throw new RangeError ( "The range("+_oPlayRange.min+","+_oPlayRange.max+") must have a length greater or equals to 1 : " + (range.max - range.min) );
			
			if( _oPlayRange.max > _nTotalFrames )
				_oPlayRange.max = _nTotalFrames;
				
			if( _oPlayRange.min < 1 ) 
				_oPlayRange.min = 1;
			
			_oPlayRange = range; 
			_nRangeLength = _oPlayRange.max - _oPlayRange.min + 1 ;
			_nRangeStart = _oPlayRange.min - 1;
		}
		
		/**
		 * Returns the current frame range.
		 * <p>
		 * If no range have been defined, the returned range
		 * is the complete animation range.
		 * </p> 
		 * @return	frame <code>Range</code> within which
		 * 			the animation run
		 */
		public function getRange () : Range
		{
			return _oPlayRange;
		}
		
		/**
		 * Returns <code>true</code> if the animation will run endless.
		 * 
		 * @return	<code>true</code> if the animation will loop endless,
		 * 			<code>false</code> otherwise
		 */
		override public function isLooping () : Boolean
		{
			return _nLoopCount == 0;
		}
		
		/**
		 * Defines the number of loops the <code>AdvancedMovieClipRT</code>
		 * will perform before its stop.
		 * <p>
		 * When you pass <code>0</code> to the function, the animation will
		 * loop indefinitely. If the passed-in number isn't a valid loop count 
		 * it's set to <code>0</code>.
		 * </p><p>
		 * Note : Adding an asynchronous listener to an animation which will
		 * run indefinitely will in that the listener will never be call. 
		 * </p> 
		 * @param	n	<code>Number</code> of animation loops before stop.
		 */
		public function setLoopCount ( n : uint ) : void
		{
			_nLoopCount = n >= 0 ? n : 0;
			_switchStrategy();
		}
		
		/**
		 * Returns the current loop count limit.
		 * 
		 * @return	<code>Number</code> of animation loops before stop
		 */
		public function getLoopCount () : uint
		{
			return _nLoopCount;
		}
		
		/**
		 * Adds <code>ASyncCommandListener</code> as listener for the
		 * <code>onCommandEnd</code> event of the current object.
		 * <p>
		 * A warning log is send on the Kairos debug channel when you
		 * try to register a listener to an animation which have no end.
		 * </p>
		 * @param	listener <code>ASyncCommandListener</code> object to add
		 * 					 as listener for the <code>onCommandEnd</code> event
		 * 					 of the current object
		 * @param	rest	 any optional arguments defined by the
		 * 					 <code>EventBroadcaster.addEventListener</code> method
		 * @return	<code>true</code> if the object have been succesfully registered
		 * 			as listener for this object, either <code>false</code>
		 */
		override public function addASyncCommandListener( listener : ASyncCommandListener, ... rest ) : Boolean
		{
			if( _nLoopCount == 0 ) 
				KairosDebug.WARN( listener + " have registered to an object which will not fire the onCommandEnd event : " + this ); 
			
			return super.addASyncCommandListener.apply( null, [ listener ].concat( rest ) );
		}
		
		/**
		 * Overrides the default animation control.
		 */
		override protected function _movePlayHead ( f : Number ) : void
		{	
			if( _oStrategy.isMotionFinished ( _nFrame, _nRangeLength, f ) )
			{
				var reversed : Boolean = isReversed();
				if( ++_nLoopNum == _nLoopCount )
		 		{
		 			_nFrame =  reversed ? 1 : _nRangeLength ;
		 			_frameChanged ( _nRangeStart + _nFrame );
		 			super.stop();   
		 			super.fireCommandEndEvent();
		 			return;
		 		}
		 		else
		 		{
		 			_nFrame = _oStrategy.getNextFrame( _nFrame, _nRangeLength , f );
		 			
			 		if( _nLoopNum + 1 == _nLoopCount )
			 		{	
			 			_oStrategy = reversed ? REVERSED_NO_LOOP : NO_LOOP;
			 		}
			 	}
			}
			else
			{
				_nFrame = _oStrategy.getNextFrame( _nFrame, _nRangeLength , f );
			}
			
			_frameChanged ( _nRangeStart + _nFrame ); 	
		}
		
		/**
		 * Overrides the default strategy choice.
		 */
		override protected function _switchStrategy () : void
		{
			var reversed : Boolean = isReversed();
			if( _nLoopCount == 0 )
			{
				_oStrategy = reversed ? REVERSED_LOOP : LOOP;
			}
			else if( _nLoopCount == 1 )
			{
				_oStrategy = reversed ? REVERSED_NO_LOOP : NO_LOOP;
			}
			else
			{
				_oStrategy = reversed ? ADVANCED_REVERSED_LOOP : ADVANCED_LOOP;
			}
		}
	}
}